/**
 * StandardMain
 * 
 * Author: Moritz Vieth, 02/2008
 * Contact: mail@moritz-vieth.de
 * 
 * 
 * Template (or rather, blueprint, more or less) of a typical main method for your
 * everyday application using a controller-based architecture (like MVC).
 * Configuration can be done via config file and commend line (overrides values in
 * the config file); an usage message is included. 
 * (Textual) output is done via a custom lib, which allows for formatted output, 
 * logging and backtracing methods. 
 * SIGTERM (ctrl-c) will be caught for a proper shutdown.
 * SIGSEGV (segmentation faults) and SIGABRT will be caught and a backtrace will be
 * printed.
 * 
 * @author Moritz Vieth
 * @version 1.0
 * 
 * This is free code, of course. Use it with (L)GPL, MIT-licence, or anything.
 * In other words: do what you like with it, but I'd appreciate if you mention my name ;) 
 * (http://www.gnu.org/licenses/gpl-3.0.html)
 * (http://www.opensource.org/licenses/mit-license.php)
 **/


/* Global includes  */
#include <signal.h>

#include <unistd.h> 
#include <getopt.h>
#include <iostream>

/* custom output library */
#include <niftout/Output.h>

/* custom utils */
#include <CvUtils/ParFile.h>
#include <CvUtils/FileUtils.h>

#include "ErrorCodes.hpp"


#include "Controller.h"


/* version and build date info */
const std::string VERSION    = "0.1";
const std::string BUILD_DATE = __DATE__ " " __TIME__;



/* global variables */
Controller* controller;
int sigTermCount;
std::string applicationInfo;
//TODO add your own

/* option values */
int debugLevel;
std::string configFileName;
std::string videoFileName;
bool useFile;
//TODO dummy values - define your own
std::string someStringOption;
int someNumOption;


/* ================================= signal handlers ================================= */

/** 
 * SIGTERM (ctrl-c) handler.
 **/
void shutDown(int code){
	sigTermCount++;
	if (sigTermCount == 1) {
		Output::message("Shutting down...", Output::VERBOSE);
		controller->shutDown();
		
	} else {
		Output::message("User impatient. Forcing exit.", Output::VERBOSE);
		exit(EXIT_SUCCESS);
	}
} 

/** 
 * SIGABRT handler.
 * (SIGABRT may be called by 'assert()' failures)
 **/ 
void abortHandler(int signal){
	Output::printBacktrace(Output::CRITICAL, 4);	
}

/**
 * SIGSEGV (Segmentation fault) handler
 **/
void termHandler(int signal){
	Output::printBacktrace("Recieved Segmentation fault!", Output::CRITICAL, 2);
	exit(EXIT_FAILURE);
}

/* =================================================================================== */



/** 
 * Print usage message.
 **/
void usage(){
	/* Do not use libniftout here, because it may not have been
	 * properly set up, and a usage message will look better
	 * this way anyways */
	 
	 //std::cout << "<Application name>: <Short application description>" << std::endl;
	 std::cout << "Standard Main: A simple blueprint for an application" << std::endl; //TODO customize
	 std::cout << std::endl;
	 std::cout << "The following command line options are available:" << std::endl;
	 std::cout << "  -h --help:               show this message" << std::endl;
	 std::cout << "  -o --output-level level: set the amount of text output." << std::endl;
	 std::cout << "           Possible values are:" << std::endl;
	 std::cout << "             DEBUG     - all the (debug) messages you don't want to see." << std::endl;
	 std::cout << "             TESTING   - still quite a lot of messages, but significantly less." << std::endl;
	 std::cout << "             VERBOSE   - \"normal\" output (what you would expect from an app.)" << std::endl;
	 std::cout << "             IMPORTANT - only messages about things that may influence the" << std::endl;
	 std::cout << "                        execution of the program" << std::endl;
	 std::cout << "             CRITICAL  - only severe errors that will most likely crash the app" << std::endl;
	 std::cout << "  -c --config-file file:  Use config file file." << std::endl;
	 std::cout << "  -f --from-file file  :  Use video file file as input." << std::endl;
	 std::cout << "  -s --string-opt:	     Any string option." << std::endl;
	 std::cout << "  -n --numerical-opt:     Any numerical option." << std::endl;
	 std::cout << std::endl;
	//TODO change according to your command line options	
}



/**
 * Set the default values for global parameters.
 * Should be done for any optional argument, arguments with optional
 * parameters and any global parameter that is not configurable via
 * the command line.
 **/ 
void setDefaults(){
	debugLevel = Output::DEBUG;
	configFileName = "config.txt";
	videoFileName = "video.mpg";
	useFile = false;
	
	//TODO dummy values - use your own
	someStringOption = "Some string that would make sense here";
	someNumOption = 0;
	
	//TODO customize
	/* This string should contain some information about the application 
	 * such as purpose and version. This information will be prepended 
	 * to each logging session and may be printed at application start.
	 * In this case it would be something like the following */
	applicationInfo = "StarterKit:\n"
				"Blueprint for a simple application using a Controller class, "
				"command line arguments and/or config files.\n\n"
				"Version: " + VERSION  + "\n"
				"Build Date: " + BUILD_DATE + "";

	
	/* These should be set here, to ensure proper output before setup is completed. */ 
	Output::setTag("Starter Kit"); //The tag will be prepended to each line //TODO customize
	Output::setOutputLevel(debugLevel);
	
}



/**
 * Read a config file.
 **/
void readConfig(int argc, char** argv){
	
	/* We cannot use the nice getopt_long here, because it may permute the argv array */
	/* See if any config file name was given */
	std::string currentArg;
	for (int i = 0; i < argc; ++i){
		currentArg.assign(argv[i]);
		if (currentArg.compare("-c") == 0 || currentArg.compare("--config-file") == 0){
			if(i + 1 < argc) { //check if this is not the last command line argument
				if(argv[i+1][0] != '-'){ //check if an actual value from the file name was provided
					configFileName.assign(argv[i+1]);
				}
			}
			break;
		}
	}
	
	
	/**
	 * use the ParFile class written by Stefan Hahne.
	 * Config files according to this class are structured like this:
	 * 
	 * [Section Name]
	 * 
	 * OptionName = OptionValue
	 * AnotherOptionName = "A quoted string value"
	 * 
	 * [Another Section]
	 * 
	 * #this is a comment telling you that whitespace is permitted
	 * Option name with whitespace = 5
	 * 
	 * ---------
	 * 
	 * You can access these values through the method GetValue:
	 * 
	 * ParFile* parFile = newParFile("config.txt");
	 * char* optionValue = parFile->GetValue("Section Name", "OptionName");
	 * 
	 * The method returns a C-Style string (use atoi/atof/etc for numerical values),
	 * or NULL if the option is not present.
	 **/
	ParFile* configFile = new ParFile(configFileName.c_str());
	
	if(configFile->isOpen()){ //check if there is any config file
		
		char* value = NULL;
		
		if((value = configFile->GetValue("Output","DebugLevel")) != NULL) { //assign value and check for existence
	        std::string valueString = value;
	        
	        if (valueString.compare("DEBUG") == 0){
	            debugLevel = Output::DEBUG;
	        } else if (valueString.compare("TESTING") == 0){
	            debugLevel = Output::TESTING;
	        } else if (valueString.compare("VERBOSE") == 0){
	            debugLevel = Output::VERBOSE;
	        } else if (valueString.compare("IMPORTANT") == 0){
	            debugLevel = Output::IMPORTANT;
	        } else if (valueString.compare("CRITICAL") == 0){
	            debugLevel = Output::CRITICAL;
	        } else if (valueString.compare("QUIET") == 0){
	            debugLevel = Output::QUIET;
	        } else {
	            Output::message("Unknown Output Level \"%s\", defaulting to \"VERBOSE\"", Output::CRITICAL, valueString.c_str());
	            debugLevel = Output::VERBOSE;
	        }
	    }
		
		//TODO customize according to your setup
		if((value = configFile->GetValue("General", "SomeNumOption")) != NULL){
	        someNumOption = atoi(value);
	    }
		
		if ((value = configFile->GetValue("General", "SomeStringOption")) != NULL) {
	        someStringOption = value;
	    }

	}
	
	delete configFile;
	
}



/**
 * Parse the command line.
 * This should be called after readConfig(), so that the command line options
 * will override values in the config file.
 */
void parseArguments(int argc, char** argv){
	
	
	/* Array defining the possible options.
	 * Has to be static; last element has to be all zeroes.
	 * See 'man getopt_long' for further info.*/
	static struct option long_options[] = {
			/*{longOptionName, argumentRequired, flag (leave at 0), shortName}*/
			{"help",         no_argument,       0, 'h'},
            {"output-level", required_argument, 0, 'o'},
            {"config-file",  required_argument, 0, 'c'},
            {"from-file",    required_argument, 0, 'f'},
            //TODO add your own
            {"string-opt",   required_argument, 0, 's'},
            {"numerical-opt",optional_argument, 0, 'n'},
            {0, 0, 0, 0}
    };
	
	/* The option string.
	 * Specifies how the command line is to be parsed. Characters are valid characters
	 * for short options (MUST be the same as in the option struct above).
	 * No colon means that the option takes no argument.
	 * If followed by one colon, the option takes one mandatory argument.
	 * If followed by two colons, the option takes one optional argument.
	 */
	std::string option_string = "hd:c:f:s:n::";
	//TODO customize
	
	
	std::string argumentValue;
	int c = 0;
	int option_index;
	
    while (c != -1) {
    	
    
    	option_index = 0;
	
		c = getopt_long (argc, argv, option_string.c_str(), 
			long_options, &option_index);
		
		/* optarg is a pointer (char *) defied in getopt.h (where getopt_long is from).
		 * if an argument to an option is given, optarg will point to it.
		 * If no argument is given, optarg will be NULL.
		 * For the sake of std::string manipulation methods, we transfer it to an actual
		 * C++ string */
		argumentValue.clear();
		if (optarg){
			argumentValue.assign(optarg);
		}
		
		switch (c) {
			case -1:
				break;
			case 'h':
				usage();
				exit(EXIT_SUCCESS);
			case 'd':
				
				if (argumentValue.compare("DEBUG") == 0 || argumentValue.compare("0") == 0) {
					debugLevel = Output::DEBUG;
				} else if (argumentValue.compare("TESTING") == 0 || argumentValue.compare("1") == 0) {
					debugLevel = Output::TESTING;
				} else if (argumentValue.compare("VERBOSE") == 0 || argumentValue.compare("2") == 0) {
					debugLevel = Output::VERBOSE;
				} else if (argumentValue.compare("IMPORTANT") == 0 || argumentValue.compare("3") == 0) {
					debugLevel = Output::IMPORTANT;
				} else if (argumentValue.compare("CRITICAL") == 0 || argumentValue.compare("4") == 0) {
					debugLevel = Output::CRITICAL;
				} else if (argumentValue.compare("QUIET") == 0) {
					debugLevel = Output::QUIET;
				}
				break;
				
			case 'c':
				/* NOTE: if -c or --config-file is set, we will have parsed that already, provided
				 * that readConfig() is called before this method (which is advisable, so that command
				 * line options can override values from the config file).
				 * We'll set it anyway, for completeness */
				configFileName.assign(optarg);
				break;

			case 'f':
				useFile = true;
				videoFileName = argumentValue;
				
				if (FileUtils::fileExists(videoFileName) == false){
					Output::message("File '%s' does not exist!", Output::CRITICAL, videoFileName.c_str());
					exit(EXIT_FAILURE);
				}
				
			//TODO add your own	
			case 's':
				someStringOption.assign(argumentValue);
				break;

			case 'n':
				if (!argumentValue.empty()) { //any argument present for that option?
					someNumOption = atoi(optarg);
				} else {
					//no argument given, take default value
				}
				break;
				
			
		
			default:
				std::cout << "Unknown option: " << (char)c << "!" << std::endl;
				usage();
				exit(EXIT_FAILURE);
		}
	}
	
	
}



/**
 * Set up the output functions (for libniftout).
 **/
void setupOutput(){
	
	
	//TODO customize - it could be good idea to put (almost) all these in a config file
	Output::setOutputLevel(debugLevel);
	Output::setLogLevel(Output::DEBUG);
	Output::setApplicationInfo(applicationInfo);
	Output::setLogPath("./log/");
	Output::showFunctionNames(true); //application has to be compiled with -pg and linked
	Output::showParams(true);        // with -rdynamic for this to work
	Output::useColors(true); //set if Output messages should be colored (i.e. IMPORTANT and CRITICAL messages
							 // will be red). Works only on ANSI terminals
}





/* ==================================================================================================== */

 
 /**
  * The main method.
  * (Well, duh!)
  */
int main( int argc, char** argv ){
	
	
	/* setup signal handling, so that your program at least crashes(/terminates) gracefully */ 
	sigTermCount = 0;
	signal(SIGINT, shutDown);
	signal(SIGABRT, abortHandler);
	signal(SIGSEGV, termHandler);
	
	
	setDefaults();
	readConfig(argc, argv);
	parseArguments(argc, argv);
	setupOutput();
	Output::printApplicationInfo();
	
	
	
	/* -------------------------- actual work starts here -------------------------- */
	controller = new Controller(); //TODO change according to actual constructor

	
	errorCodes::errorCode returnedCode;
	
	returnedCode = controller->init(useFile, videoFileName); //TODO pass your option values
	if(returnedCode != errorCodes::SUCCESS){
		Output::message("Error %d (%s) occured during init, exiting.", Output::CRITICAL, returnedCode, errorCodes::getCodeString(returnedCode).c_str());								
		return EXIT_FAILURE;				
	}	
	
	/* The controller runs as a thread */ 
	controller->start(NULL);
	
	/* wait for the controller thread to end */
	controller->join();
	
			
	      
	return EXIT_SUCCESS;
}
